var Matrix = (function () {
    'use strict';

    var mismatch = 'dimensions mismatch in matrix operation';

    function Matrix(m) {
        var i;
        this.m = m;

        this.r = m.length;
        this.c = 0;
        for (i = 0; i < this.r; i += 1) {
            if (this.m[i].length > this.c) {
                this.c = this.m[i].length;
            }
        }

        for (i = 0; i < this.r; i += 1) {
            while (this.m[i].length < this.c) {
                this.m[i].push(0);
            }
        }
    }

    Matrix.zeros = function (n, o) {
        var m = [], r, c;
        if (o === undefined) {
            o = n;
        }

        for (r = 0; r < n; r += 1) {
            m[r] = [];
            for (c = 0; c < o; c += 1) {
                m[r][c] = 0;
            }
        }

        return new Matrix(m);
    };

    Matrix.eye = function (n) {
        var m = Matrix.zeros(n), r;

        for (r = 0; r < n; r += 1) {
            m.m[r][r] = 1;
        }

        return m;
    };

    Matrix.prototype.times = function (y) {
        var z, m, n, o, r, cm, cy;
        if (y instanceof Matrix) {
            if (y.r !== this.c) {
                throw new Error(mismatch);
            }

            // Matrix (mxn) * Matrix (nxo)
            m = this.r;
            n = this.c;
            o = y.c;

            z = [];
            for (r = 0; r < m; r += 1) {
                z[r] = [];
                for (cm = 0; cm < n; cm += 1) {
                    for (cy = 0; cy < o; cy += 1) {
                        z[r][cy] = z[r][cy] || 0;
                        z[r][cy] += this.m[r][cm] * y.m[cm][cy];
                    }
                }
            }
            return new Matrix(z);
        }

        if (y instanceof Array) {
            z = [];
            if (y.length !== this.c) {
                throw new Error(mismatch);
            }

            // Matrix * Vector
            // mxn * nx1
            m = this.r;
            n = this.c;
            for (r = 0; r < this.m.length; r += 1) {
                z[r] = 0;
                for (cm = 0; cm < n; cm += 1) {
                    z[r] += this.m[r][cm] * y[cm];
                }
            }

            return z;
        }

        throw new Error('invalid argument');
    };

    return Matrix;
}());
